package com.cicadasmall.security.config;


import com.cicadasmall.common.func.Fn;
import com.cicadasmall.common.resp.R;
import com.cicadasmall.security.SecurityUrlProperties;
import com.cicadasmall.security.handler.RestAuthExceptionEntryPoint;
import com.cicadasmall.security.handler.RestLoginSuccessHandler;
import com.cicadasmall.security.handler.RestOauth2LogoutHandler;
import com.cicadasmall.security.support.provider.ConnectAuthProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.web.cors.CorsUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;


/**
 * SecurityConfig
 *
 * @author Jin
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties(SecurityUrlProperties.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsServiceImpl;

    @Autowired
    private RestOauth2LogoutHandler restOauth2LogoutHandler;

    @Autowired
    private SecurityUrlProperties securityUrlProperties;

    @Autowired
    private ClientDetailsService redisClientDetailsService;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    @Autowired(required = false)
    private List<ConnectAuthProvider> connectAuthProviderList;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 登陆成功处理器
     *
     * @return AuthenticationSuccessHandler
     */
    @Bean
    public AuthenticationSuccessHandler loginSuccessHandler() {
        return new SavedRequestAwareAuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                super.onAuthenticationSuccess(request, response, authentication);
            }
        };
    }

    /***
     *
     * 手机号认证成功处理器
     *
     * @return AuthenticationSuccessHandler
     */
    @Bean
    public AuthenticationSuccessHandler restLoginSuccessHandler() {
        return new RestLoginSuccessHandler(objectMapper, passwordEncoder(), redisClientDetailsService, authorizationServerTokenServices);
    }

    /**
     * 登陆失败处理器
     *
     * @return AuthenticationFailureHandler
     */
    @Bean
    public AuthenticationFailureHandler restLoginFailureHandler() {
        return (request, response, exception) -> {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.getWriter().write(objectMapper.writeValueAsString(R.error().setMessage(exception.getMessage())));
            response.getWriter().flush();
            response.getWriter().close();
        };

    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceImpl)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(WebSecurity webSecurity) {
        webSecurity
                .ignoring()
                .antMatchers("/signIn")
                .antMatchers("/index.html")
                .antMatchers("/error")
                .antMatchers("/oauth/user/token")
                .antMatchers("/oauth/mobile/token")
                .antMatchers("/oauth/wxma/token")
                .antMatchers("/oauth/wxmp/token")
                .antMatchers("/oauth/code/token")
                .antMatchers("/oauth/client/token")
                .antMatchers("/verify/**")
                .antMatchers(HttpMethod.OPTIONS)
                .antMatchers(securityUrlProperties.getIgnored());

    }


    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .formLogin()
                .loginPage("/account/signIn")
                .loginProcessingUrl("/account/signIn")
                .permitAll()
                .successHandler(loginSuccessHandler())
                .failureHandler(restLoginFailureHandler())
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(new RestAuthExceptionEntryPoint(objectMapper))
                .and()
                .cors()
                .and()
                .logout()
                .addLogoutHandler(restOauth2LogoutHandler)
                .clearAuthentication(true)
                .and()
                .authorizeRequests()
                .requestMatchers(CorsUtils::isPreFlightRequest)
                .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers(securityUrlProperties.getAuthenticated())
                .authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .csrf()
                .disable()
                .headers()
                .frameOptions()
                .disable()
                .cacheControl()
        ;

        if (Fn.isNotEmpty(connectAuthProviderList)) {
            connectAuthProviderList.forEach(connectAuthProvider -> httpSecurity.authenticationProvider(connectAuthProvider));
        }
    }


}
